// xrender.h

#pragma once

#pragma comment(lib, "D3d9.lib")
#pragma comment(lib, "D3dx9.lib")
#include <d3d9.h>
#include <d3dx9.h>


#include "common\dxdebug.h"

#include "xtxtloader.h"


extern LPDIRECT3DDEVICE9 g_pD3DDevice;
extern bool g_drawBones;


extern int g_numMeshesRendered;		// xrender.cpp
extern int g_numTriCount;			// xrender.cpp
extern int g_materialSwaps;			// xrender.cpp

#define MAX_BONE_WEIGHTS	7

inline void RenderMesh(const stXFile* xFile, const stXFrame* xFrame, const stXMesh* xMesh)
{
	if (xMesh->m_numVerts==0 || xMesh->m_numTris==0) return;

	g_numMeshesRendered++;
	g_numTriCount+=xMesh->m_numTris;


	D3DXMATRIX identity;
	D3DXMatrixIdentity(&identity);
	g_pD3DDevice->SetTransform(D3DTS_WORLD, &identity);


	#pragma pack(1)
	struct stVertex
	{
		D3DXVECTOR3 pos;
		D3DXVECTOR3 norm;
		D3DXVECTOR2 tex;

		D3DXVECTOR3 skinPos[MAX_BONE_WEIGHTS];
		float		weight[MAX_BONE_WEIGHTS];
		int count;

		enum { FVF_TVERTEX = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 };
	};
	#pragma pack()


	const int numVerts			= xMesh->m_numVerts;
	stVertex* vertex			= new stVertex[numVerts + 5]; // +5 is safety buffer

	const int numNorms			= xMesh->m_numNormals;
	
	const int numTexCoords		= xMesh->m_numTexCoords;

	const int numTris			= xMesh->m_numTris;
	const stXMesh::stTriData* tris	= xMesh->m_tris;

	unsigned int* triIndices	= new unsigned int[numTris * 3 + 100]; // +100 is a sanity check

	unsigned int* startIndices	= triIndices;
	unsigned int* endIndices	= triIndices + (numTris*3);


	for (int i=0; i<numTris; i++)
	{
		for (int k=0; k<3; k++)
		{
			triIndices[i*3 + k] = tris[i].vertIndx[k];
		}
	}


	for (int i=0; i<numVerts; i++)
	{
		vertex[i].pos.x		= xMesh->m_verts[i].x;
		vertex[i].pos.y		= xMesh->m_verts[i].y;
		vertex[i].pos.z		= xMesh->m_verts[i].z;
		vertex[i].norm      = D3DXVECTOR3(0,0,0);
		vertex[i].count     = 0;

		if (numTexCoords>0)
		{
			DBG_ASSERT(numTexCoords==numVerts);
			vertex[i].tex = xMesh->m_texCoords[i];
		}
	}

	// We arn't doing per face normals at the moment, so we just
	// average the normals across the vertices from the faces
	if (xMesh->m_numNormals>0)
	for (int i=0; i<numTris; i++)
	{
		for (int k=0; k<3; k++)
		{
			DBG_ASSERT(xMesh->m_numNormals == numTris);

			const int faceCorner		= tris[i].vertIndx[k];
			const D3DXVECTOR3 normVec	= tris[i].normals[k];

			DBG_ASSERT(faceCorner>=0 && faceCorner<numVerts);
			vertex[faceCorner].norm += normVec;
			vertex[faceCorner].count++;
		}
	}

	for (int i=0; i<numVerts; i++)
	{
		if (vertex[i].count>0)
		{
			vertex[i].norm *= 1.0f/(vertex[i].count);
			vertex[i].count = 0;
		}
	}


	stXSkinWeights* skinWeights = xMesh->m_skinWeights;
	while (skinWeights)
	{
		const char* skinName = skinWeights->m_name;

		const stXFrame* xTopFrame = &xFile->m_frame;

		const stXFrame* xFrame = FindFrame(xTopFrame, skinName);
		DBG_ASSERT(xFrame);
		if (xFrame==NULL)
		{
			skinWeights = skinWeights->m_next;
			continue;
		}

		const D3DXMATRIX matFrame = xFrame->m_matAnim;


		int* indexes   = skinWeights->m_indexes;
		float* weights = skinWeights->m_weights;
		D3DXMATRIX matSkin = skinWeights->m_mat;

#if 1 // Animation of bones
		if (g_drawBones)
		{
		D3DXMATRIX matBone = xFrame->m_matAnim;
		D3DXVECTOR3 bonePosF = D3DXVECTOR3(matBone._41, matBone._42, matBone._43);
		DebugDrawCross3D(g_pD3DDevice, bonePosF, 3, 0xffff00ff);

		stXFrame* childs = xFrame->m_child;
		while (childs)
		{
			D3DXMATRIX childMat = childs->m_matAnim;
			D3DXVECTOR3  bonePosTo = D3DXVECTOR3(childMat._41, childMat._42, childMat._43);

			DebugDrawLine3D(g_pD3DDevice, bonePosF, bonePosTo, 0xffff00ff);
			childs = childs->m_next;
		}
		}
#endif

#if 0 // Static Default Pose of Bones
		{
		D3DXMATRIX matBone = xFrame->m_matTrans;
		D3DXVECTOR3 bonePosF = D3DXVECTOR3(matBone._41, matBone._42, matBone._43);
		DebugDrawCross3D(g_pD3DDevice, bonePosF, 3, 0xffff00ff);

		stXFrame* childs = xFrame->m_child;
		while (childs)
		{
			D3DXMATRIX childMat = xFrame->m_matTrans;
			D3DXVECTOR3  bonePosTo = D3DXVECTOR3(childMat._41, childMat._42, childMat._43);

			DebugDrawLine3D(g_pD3DDevice, bonePosF, bonePosTo, 0xffffff00);
			childs = childs->m_next;
		}
		}
#endif

		for (int i=0; i<skinWeights->m_numIndexes; i++)
		{
			int   index      = indexes[i];
			float weight     = weights[i];

			DBG_ASSERT(index>=0 && index<numVerts);
			

			DBG_ASSERT(vertex[index].count<MAX_BONE_WEIGHTS);

			D3DXMATRIX matTrans = (matSkin * matFrame);

			vertex[index].weight[vertex[index].count] = weight;


			D3DXVec3TransformCoord(	&vertex[index].skinPos[vertex[index].count], 
									&vertex[index].pos, &matTrans);
			
			vertex[index].count++;
		}

		skinWeights = skinWeights->m_next;
	}


	for (int i=0; i<numVerts; i++)
	{
		int numWeights = vertex[i].count;
		if (numWeights==0)
		{
			//D3DXVec3TransformCoord(	&vertex[i].pos, &vertex[i].pos, &xFrame->m_matTrans);
			D3DXVec3TransformCoord(	&vertex[i].pos, &vertex[i].pos, &xFrame->m_matAnim);
		}
		else
		{
			D3DXVECTOR3 pos = D3DXVECTOR3(0,0,0);
			for (int k=0; k<numWeights; k++)
			{
				pos += vertex[i].skinPos[k] * vertex[i].weight[k];
			}

			vertex[i].pos = pos;
		}
	}

#if 1 // Draw Normals
	extern bool g_drawNormals;
	if (g_drawNormals)
	for (int i=0; i<numVerts; i++)
	{
		DebugDrawLine3D(g_pD3DDevice, 
						vertex[i].pos, 
						vertex[i].pos + vertex[i].norm,
						0xffffff00);
	}
#endif

#if 1
	for (int i=0; i<numTris; i++)
	{
		DBG_ASSERT(triIndices[i]>=0 && (int)triIndices[i]<numVerts);
	}
#endif

	g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
	//g_pD3DDevice->SetTransform( D3DTS_WORLD, mat );
	g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	//g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	g_pD3DDevice->SetTexture( 0, NULL );
	g_pD3DDevice->SetFVF( stVertex::FVF_TVERTEX );

	if (xMesh->m_matList)
	{
		
		stXMaterialList* matList = xMesh->m_matList;
		int numFaces = matList->m_numIndexes;
		int* matIds  = matList->m_indexes;
		DBG_ASSERT(numFaces==numTris);
		const stMaterialManager* matManager = &xFile->m_materialManager;

		int curMatId  = -1;
		int startIndx =  0;
		int endIndx   =  0;
		D3DMATERIAL9 dxMat;
		memset(&dxMat, 0, sizeof(D3DMATERIAL9));


		g_pD3DDevice->SetRenderState(D3DRS_COLORVERTEX, FALSE);
		//g_pD3DDevice->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);
		//g_pD3DDevice->SetRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL);
		//g_pD3DDevice->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL);
		g_pD3DDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE , D3DMCS_MATERIAL);


		g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
		g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
		g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);

		// Add alpha stage
		// 0
		g_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP,  D3DTOP_MODULATE );
		g_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,D3DTA_TEXTURE );
		g_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2,D3DTA_DIFFUSE );

		// 1
		g_pD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAOP,  D3DTOP_ADD);
		g_pD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAARG1,D3DTA_CURRENT );
		g_pD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAARG2,D3DTA_SPECULAR);

		g_pD3DDevice->SetTextureStageState(2, D3DTSS_ALPHAOP,  D3DTOP_DISABLE );



		for (int i=0; i<numFaces; i++)
		{
			int matId = matIds[i];
			DBG_ASSERT(matId>=0 && matId<1000); // Sanity check

			if (matId!=curMatId)
			{
				
				if (curMatId!=-1)
				{
					endIndx   = i;

					int numTrisMat = (endIndx - startIndx);
					int offset     = 3*startIndx;
					DBG_ASSERT(numTrisMat>0 && numTrisMat<numTris);

					unsigned int* startMatInds = triIndices + offset;
					DBG_ASSERT(startMatInds>=startIndices && startMatInds<endIndices);
					g_pD3DDevice->SetMaterial(&dxMat);

					g_pD3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, // D3DPRIMITIVETYPE PrimitiveType,
														 0,					 // UINT MinVertexIndex,
														 numVerts,			 // UINT NumVertices,
														 numTrisMat,		 // UINT PrimitiveCount,
														 startMatInds,		 // CONST void * pIndexData,
														 D3DFMT_INDEX32,	 // D3DFORMAT IndexDataFormat,
														 vertex,			 // CONST void* pVertexStreamZeroData,
														 sizeof(stVertex));	 // UINT VertexStreamZeroStride

				}


				startIndx = i;
				endIndx   = numFaces;

				curMatId = matId;

				stXMaterial* xMat = matManager->GetMaterial(matId);
				DBG_ASSERT(xMat);

				dxMat.Diffuse.r = xMat->m_faceColourRGBA[0];
				dxMat.Diffuse.g = xMat->m_faceColourRGBA[1];
				dxMat.Diffuse.b = xMat->m_faceColourRGBA[2];
				dxMat.Diffuse.a = xMat->m_faceColourRGBA[3];
				dxMat.Ambient = dxMat.Diffuse;
				g_pD3DDevice->SetTexture( 0, xMat->m_pTexture );
				
				extern bool g_transparent;
				if (g_transparent)
				{
					dxMat.Ambient.a = dxMat.Diffuse.a = 0.2f;
				}

				g_materialSwaps++;
			}
		}

		DBG_ASSERT(curMatId!=-1);
		if (curMatId!=-1)
		{
			int numTrisMat = (endIndx - startIndx);
			DBG_ASSERT(numTrisMat>0 && numTrisMat<=numTris);
			int offset     = 3*startIndx;


			unsigned int* startMatInds = triIndices + offset;
			DBG_ASSERT(startMatInds>=startIndices && startMatInds<endIndices);

			g_pD3DDevice->SetMaterial(&dxMat);
			g_pD3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, // D3DPRIMITIVETYPE PrimitiveType,
												 0,					 // UINT MinVertexIndex,
												 numVerts,			 // UINT NumVertices,
												 numTrisMat,		 // UINT PrimitiveCount,
												 startMatInds,		 // CONST void * pIndexData,
												 D3DFMT_INDEX32,	 // D3DFORMAT IndexDataFormat,
												 vertex,			 // CONST void* pVertexStreamZeroData,
												 sizeof(stVertex));	 // UINT VertexStreamZeroStride

		}

	}
	else
	{
#if 1

		//g_pD3DDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );


		D3DMATERIAL9 dxMat;
		memset(&dxMat, 0, sizeof(D3DMATERIAL9));
		dxMat.Diffuse.r = 1;
		dxMat.Diffuse.g = 0;
		dxMat.Diffuse.b = 0;
		dxMat.Diffuse.a = 1;
		dxMat.Ambient = dxMat.Diffuse;
		g_pD3DDevice->SetMaterial(&dxMat);

		g_pD3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, // D3DPRIMITIVETYPE PrimitiveType,
											 0,					 // UINT MinVertexIndex,
											 numVerts,			 // UINT NumVertices,
											 numTris,			 // UINT PrimitiveCount,
											 triIndices,		 // CONST void * pIndexData,
											 D3DFMT_INDEX32,	 // D3DFORMAT IndexDataFormat,
											 vertex,			 // CONST void* pVertexStreamZeroData,
											 sizeof(stVertex));	 // UINT VertexStreamZeroStride


#endif
	}									



	delete[] triIndices;
	delete[] vertex;
}


inline void XRenderFrame(const stXFile* xFile, const stXBase* xBase)
{
	DBG_ASSERT(xBase);
	DBG_ASSERT(xBase->GetType()==X_FRAME);


	const stXFrame* xFrame = static_cast<const stXFrame*>(xBase);
	DBG_ASSERT(xFrame);

	
	while (xFrame)
	{
		if (xFrame->m_child)
		{
			DBG_ASSERT(xBase!=xFrame->m_child);
			XRenderFrame(xFile, xFrame->m_child);
		}

		stXMesh* xMesh = xFrame->m_mesh;
		while (xMesh)
		{
			RenderMesh(xFile, xFrame, xMesh);
			xMesh = xMesh->m_next;
		}

		xFrame = xFrame->m_next;
	}
}

inline void XUpdate(stXFile* xFile)
{
	stXAnimSet* animSet = xFile->m_animSet;
	if (animSet)
	{
		xFile->m_curAnimSet = animSet;

		stXAnimation* boneAnim = xFile->m_curAnimSet->m_anims;
		if (boneAnim)
		{
			DBG_ASSERT(boneAnim->m_animKey);
			if (boneAnim->m_animKey)
			{
				static float animFrame = 0.0f;
				animFrame += 0.1f;
				xFile->m_frameNo = (int)animFrame;


				const int maxFrames = boneAnim->m_animKey->m_numFrames;


				if (xFile->m_frameNo >= maxFrames)
				{
					xFile->m_frameNo = 0;
					animFrame = 0.0f;
				}
			}
		}
	}


	stXFrame* xFrame = &xFile->m_frame;

	BuildFrameMatHierachy(xFile, NULL, xFrame);
}

inline void XRender(const stXFile* xFile)
{
	g_numMeshesRendered = 0;
	g_materialSwaps     = 0;
	g_numTriCount		= 0;

	DBG_ASSERT(xFile);

	const stXFrame* xFrame = &xFile->m_frame;


	XRenderFrame(xFile, xFrame);
}

